Import jsonwrt code from util-linux
authorColin Walters <walters@verbum.org>
Thu, 26 Jun 2025 11:48:38 +0000 (07:48 -0400)
committerColin Walters <walters@verbum.org>
Thu, 26 Jun 2025 14:40:42 +0000 (10:40 -0400)
We've had a longstanding need to emit JSON to be friendlier
to shell scripting tools.

I hesitated for (way too long) on which JSON libraries to
use, but actually since we don't need to *parse* JSON,
we only need to omit it, the problem domain is super
simple.

util-linux has simple code for this
https://github.com/util-linux/util-linux/commit/b649ff92d2d91864e85a5bba4c89d52236817d9e

So this commit imports it, but it will need a little massaging
to build.

Signed-off-by: Colin Walters <walters@verbum.org>
Makefile-otutil.am
src/libotutil/ul-jsonwrt.c [new file with mode: 0644]
src/libotutil/ul-jsonwrt.h [new file with mode: 0644]

index 291a2e807b051d4096c428e4f7e0c44d814fedda..86c1f6f94f7b0d4295d839104270d1122f241b41 100644 (file)
@@ -41,6 +41,8 @@ libotutil_la_SOURCES = \
        src/libotutil/otutil.h \
        src/libotutil/ot-tool-util.c \
        src/libotutil/ot-tool-util.h \
+       src/libotutil/ul-jsonwrt.h \
+       src/libotutil/ul-jsonwrt.c \
        $(NULL)
 
 if USE_GPGME
diff --git a/src/libotutil/ul-jsonwrt.c b/src/libotutil/ul-jsonwrt.c
new file mode 100644 (file)
index 0000000..365d845
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * JSON output formatting functions.
+ *
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <cctype.h>
+
+#include "c.h"
+#include "jsonwrt.h"
+
+/*
+ * Requirements enumerated via testing (V8, Firefox, IE11):
+ *
+ * var charsToEscape = [];
+ * for (var i = 0; i < 65535; i += 1) {
+ *     try {
+ *             JSON.parse('{"sample": "' + String.fromCodePoint(i) + '"}');
+ *     } catch (e) {
+ *             charsToEscape.push(i);
+ *     }
+ * }
+ */
+static void fputs_quoted_case_json(const char *data, FILE *out, int dir, size_t size)
+{
+       const char *p;
+
+       fputc('"', out);
+       for (p = data; p && *p && (!size || p < data + size); p++) {
+
+               const unsigned int c = (unsigned int) *p;
+
+               /* From http://www.json.org
+                *
+                * The double-quote and backslashes would break out a string or
+                * init an escape sequence if not escaped.
+                *
+                * Note that single-quotes and forward slashes, while they're
+                * in the JSON spec, don't break double-quoted strings.
+                */
+               if (c == '"' || c == '\\') {
+                       fputc('\\', out);
+                       fputc(c, out);
+                       continue;
+               }
+
+               /* All non-control characters OK; do the case swap as required. */
+               if (c >= 0x20) {
+                       /*
+                        * Don't use locale sensitive ctype.h functions for regular
+                        * ASCII chars, because for example with Turkish locale
+                        * (aka LANG=tr_TR.UTF-8) toupper('I') returns 'I'.
+                        */
+                       if (c <= 127)
+                               fputc(dir ==  1 ? c_toupper(c) :
+                                     dir == -1 ? c_tolower(c) : *p, out);
+                       else
+                               fputc(dir ==  1 ? toupper(c) :
+                                     dir == -1 ? tolower(c) : *p, out);
+                       continue;
+               }
+
+               /* In addition, all chars under ' ' break Node's/V8/Chrome's, and
+                * Firefox's JSON.parse function
+                */
+               switch (c) {
+                       /* Handle short-hand cases to reduce output size.  C
+                        * has most of the same stuff here, so if there's an
+                        * "Escape for C" function somewhere in the STL, we
+                        * should probably be using it.
+                        */
+                       case '\b':
+                               fputs("\\b", out);
+                               break;
+                       case '\t':
+                               fputs("\\t", out);
+                               break;
+                       case '\n':
+                               fputs("\\n", out);
+                               break;
+                       case '\f':
+                               fputs("\\f", out);
+                               break;
+                       case '\r':
+                               fputs("\\r", out);
+                               break;
+                       default:
+                               /* Other assorted control characters */
+                               fprintf(out, "\\u00%02x", c);
+                               break;
+               }
+       }
+       fputc('"', out);
+}
+
+#define fputs_quoted_json(_d, _o)       fputs_quoted_case_json(_d, _o, 0, 0)
+#define fputs_quoted_json_upper(_d, _o) fputs_quoted_case_json(_d, _o, 1, 0)
+#define fputs_quoted_json_lower(_d, _o) fputs_quoted_case_json(_d, _o, -1, 0)
+
+void ul_jsonwrt_init(struct ul_jsonwrt *fmt, FILE *out, int indent)
+{
+       fmt->out = out;
+       fmt->indent = indent;
+       fmt->after_close = 0;
+}
+
+int ul_jsonwrt_is_ready(struct ul_jsonwrt *fmt)
+{
+       return fmt->out == NULL ? 0 : 1;
+}
+
+void ul_jsonwrt_indent(struct ul_jsonwrt *fmt)
+{
+       int i;
+
+       for (i = 0; i < fmt->indent; i++)
+               fputs("   ", fmt->out);
+}
+
+static void print_name(struct ul_jsonwrt *fmt, const char *name)
+{
+       if (name) {
+               if (fmt->after_close)
+                       fputs(",\n", fmt->out);
+               ul_jsonwrt_indent(fmt);
+               fputs_quoted_json_lower(name, fmt->out);
+       } else {
+               if (fmt->after_close)
+                       fputs(",", fmt->out);
+               else
+                       ul_jsonwrt_indent(fmt);
+       }
+}
+
+void ul_jsonwrt_open(struct ul_jsonwrt *fmt, const char *name, int type)
+{
+       print_name(fmt, name);
+
+       switch (type) {
+       case UL_JSON_OBJECT:
+               fputs(name ? ": {\n" : "{\n", fmt->out);
+               fmt->indent++;
+               break;
+       case UL_JSON_ARRAY:
+               fputs(name ? ": [\n" : "[\n", fmt->out);
+               fmt->indent++;
+               break;
+       case UL_JSON_VALUE:
+               fputs(name ? ": " : " ", fmt->out);
+               break;
+       }
+       fmt->after_close = 0;
+}
+
+void ul_jsonwrt_empty(struct ul_jsonwrt *fmt, const char *name, int type)
+{
+       print_name(fmt, name);
+
+       switch (type) {
+       case UL_JSON_OBJECT:
+               fputs(name ? ": {}" : "{}", fmt->out);
+               break;
+       case UL_JSON_ARRAY:
+               fputs(name ? ": []" : "[]", fmt->out);
+               break;
+       case UL_JSON_VALUE:
+               fputs(name ? ": null" : "null", fmt->out);
+               break;
+       }
+
+       fmt->after_close = 1;
+}
+
+void ul_jsonwrt_close(struct ul_jsonwrt *fmt, int type)
+{
+       assert(fmt->indent > 0);
+
+       switch (type) {
+       case UL_JSON_OBJECT:
+               fmt->indent--;
+               fputc('\n', fmt->out);
+               ul_jsonwrt_indent(fmt);
+               fputs("}", fmt->out);
+               if (fmt->indent == 0)
+                       fputs("\n", fmt->out);
+               break;
+       case UL_JSON_ARRAY:
+               fmt->indent--;
+               fputc('\n', fmt->out);
+               ul_jsonwrt_indent(fmt);
+               fputs("]", fmt->out);
+               break;
+       case UL_JSON_VALUE:
+               break;
+       }
+
+       fmt->after_close = 1;
+}
+
+
+void ul_jsonwrt_flush(struct ul_jsonwrt *fmt)
+{
+       fflush(fmt->out);
+}
+
+void ul_jsonwrt_value_raw(struct ul_jsonwrt *fmt,
+                       const char *name, const char *data)
+{
+       ul_jsonwrt_value_open(fmt, name);
+       if (data && *data)
+               fputs(data, fmt->out);
+       else
+               fputs("null", fmt->out);
+       ul_jsonwrt_value_close(fmt);
+}
+
+void ul_jsonwrt_value_s(struct ul_jsonwrt *fmt,
+                       const char *name, const char *data)
+{
+       ul_jsonwrt_value_open(fmt, name);
+       if (data && *data)
+               fputs_quoted_json(data, fmt->out);
+       else
+               fputs("null", fmt->out);
+       ul_jsonwrt_value_close(fmt);
+}
+
+void ul_jsonwrt_value_s_sized(struct ul_jsonwrt *fmt,
+                             const char *name, const char *data, size_t size)
+{
+       ul_jsonwrt_value_open(fmt, name);
+       if (data && *data)
+               fputs_quoted_case_json(data, fmt->out, 0, size);
+       else
+               fputs("null", fmt->out);
+       ul_jsonwrt_value_close(fmt);
+}
+
+void ul_jsonwrt_value_u64(struct ul_jsonwrt *fmt,
+                       const char *name, uint64_t data)
+{
+       ul_jsonwrt_value_open(fmt, name);
+       fprintf(fmt->out, "%"PRIu64, data);
+       ul_jsonwrt_value_close(fmt);
+}
+
+void ul_jsonwrt_value_double(struct ul_jsonwrt *fmt,
+                       const char *name, long double data)
+{
+       ul_jsonwrt_value_open(fmt, name);
+       fprintf(fmt->out, "%Lg", data);
+       ul_jsonwrt_value_close(fmt);
+}
+
+void ul_jsonwrt_value_boolean(struct ul_jsonwrt *fmt,
+                       const char *name, int data)
+{
+       ul_jsonwrt_value_open(fmt, name);
+       fputs(data ? "true" : "false", fmt->out);
+       ul_jsonwrt_value_close(fmt);
+}
diff --git a/src/libotutil/ul-jsonwrt.h b/src/libotutil/ul-jsonwrt.h
new file mode 100644 (file)
index 0000000..f156fdd
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ */
+#ifndef UTIL_LINUX_JSONWRT_H
+#define UTIL_LINUX_JSONWRT_H
+
+enum {
+       UL_JSON_OBJECT,
+       UL_JSON_ARRAY,
+       UL_JSON_VALUE
+};
+
+struct ul_jsonwrt {
+       FILE *out;
+       int indent;
+
+       unsigned int after_close :1;
+};
+
+void ul_jsonwrt_init(struct ul_jsonwrt *fmt, FILE *out, int indent);
+int ul_jsonwrt_is_ready(struct ul_jsonwrt *fmt);
+void ul_jsonwrt_indent(struct ul_jsonwrt *fmt);
+void ul_jsonwrt_open(struct ul_jsonwrt *fmt, const char *name, int type);
+void ul_jsonwrt_close(struct ul_jsonwrt *fmt, int type);
+void ul_jsonwrt_empty(struct ul_jsonwrt *fmt, const char *name, int type);
+void ul_jsonwrt_flush(struct ul_jsonwrt *fmt);
+
+#define ul_jsonwrt_root_open(_f)       ul_jsonwrt_open(_f, NULL, UL_JSON_OBJECT)
+#define ul_jsonwrt_root_close(_f)      ul_jsonwrt_close(_f, UL_JSON_OBJECT)
+
+#define ul_jsonwrt_array_open(_f, _n)  ul_jsonwrt_open(_f, _n, UL_JSON_ARRAY)
+#define ul_jsonwrt_array_close(_f)     ul_jsonwrt_close(_f, UL_JSON_ARRAY)
+#define ul_jsonwrt_array_empty(_f, _n) ul_jsonwrt_empty(_f, _n, UL_JSON_ARRAY)
+
+#define ul_jsonwrt_object_open(_f, _n) ul_jsonwrt_open(_f, _n, UL_JSON_OBJECT)
+#define ul_jsonwrt_object_close(_f)    ul_jsonwrt_close(_f, UL_JSON_OBJECT)
+#define ul_jsonwrt_object_empty(_f, _n)        ul_jsonwrt_empty(_f, _n, UL_JSON_OBJECT)
+
+#define ul_jsonwrt_value_open(_f, _n)  ul_jsonwrt_open(_f, _n, UL_JSON_VALUE)
+#define ul_jsonwrt_value_close(_f)     ul_jsonwrt_close(_f, UL_JSON_VALUE)
+
+void ul_jsonwrt_value_raw(struct ul_jsonwrt *fmt,
+                       const char *name, const char *data);
+void ul_jsonwrt_value_s(struct ul_jsonwrt *fmt,
+                       const char *name, const char *data);
+void ul_jsonwrt_value_s_sized(struct ul_jsonwrt *fmt,
+                             const char *name, const char *data, size_t size);
+void ul_jsonwrt_value_u64(struct ul_jsonwrt *fmt,
+                       const char *name, uint64_t data);
+void ul_jsonwrt_value_double(struct ul_jsonwrt *fmt,
+                       const char *name, long double data);
+void ul_jsonwrt_value_boolean(struct ul_jsonwrt *fmt,
+                       const char *name, int data);
+
+#define ul_jsonwrt_value_null(_f, _n)  ul_jsonwrt_empty(_f, _n, UL_JSON_VALUE)
+
+#endif /* UTIL_LINUX_JSONWRT_H */
+